/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adri�n Noguero (adrian.noguero@esi.es)
 *     
 *******************************************************************************/
package es.esi.gemde.modeltransformator.mofscriptengine;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.mofscript.editor.MofScriptEditorPlugin;
import org.eclipse.mofscript.editor.MofScriptPreferences;
import org.eclipse.mofscript.parser.MofScriptParseError;
import org.eclipse.mofscript.parser.ParserUtil;
import org.eclipse.mofscript.runtime.ExecutionManager;
import org.eclipse.mofscript.runtime.ExecutionMessageListener;
import org.eclipse.mofscript.runtime.MofScriptExecutionException;

import es.esi.gemde.modeltransformator.exceptions.TransformationEngineException;
import es.esi.gemde.modeltransformator.service.ITransformation;
import es.esi.gemde.modeltransformator.service.ITransformationEngine;

/**
 * Implementation of the {@link ITransformationEngine} interface for MOFScript.
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0
 * @since 1.0
 *
 */
public class MOFScriptEngine implements ITransformationEngine {

	/**
	 * Constant value of the engine name. Its value is "MOFScript".
	 */
	private final String ENGINE_NAME = "MOFScript";
	private final String PLUGIN_ID = "es.esi.gemde.modeltransformator.mofscriptengine";
	
	// Used to log MOFScript transformation messages
	private String logs = new String();

	
	public MOFScriptEngine () {
		// Adds an output listener for the transformation execution.
		ExecutionManager.getExecutionManager().getExecutionStack().addOutputMessageListener(messageLogger);
		
		// Create a temporary directory to store transformations
		IPath wsPath = ResourcesPlugin.getWorkspace().getRoot().getLocation();
		
		// Create the directories if needed
		File dir = wsPath.append(".metadata/.plugins").toFile();
		if (!dir.exists() || !dir.isDirectory()) {
			dir.mkdir();
		}
		
		dir = wsPath.append(".metadata/.plugins/" + PLUGIN_ID).toFile();
		if (!dir.exists() || !dir.isDirectory()) {
			dir.mkdir();
		}
		
		// This call is required due to a bug in MOFScript source code.
		// Should be fixed...
		ExecutionManager.getExecutionManager().getResourceSet().setResourceFactoryRegistry(Resource.Factory.Registry.INSTANCE);
	}
	
	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.ITransformationEngine#checkTransformationFileValidity(java.io.File)
	 */
	@Override
	public boolean checkTransformationURIValidity(List<URI> uris) {
		
		// Copy transformation files to the temporal directory
		Vector<URI> copiedFiles = new Vector<URI>();
		
		for (URI uri : uris) {
			try {
				BufferedReader r = new BufferedReader(new InputStreamReader(uri.toURL().openStream()));
				File outFile = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/" + new File(uri).getName()).toFile();
				BufferedWriter w = new BufferedWriter(new FileWriter(outFile));
				char [] buffer = new char [1024];
				int read;
				while ((read = r.read(buffer)) != -1) {
					w.write(buffer, 0, read);
					w.flush();
				}
				r.close();
				w.close();
				copiedFiles.add(outFile.toURI());
			} catch (Exception e) {
				return false;
			}
		}
		
		// Get the main transformation file
		URI mainModule = getMainTransformation(copiedFiles);
		if (mainModule == null) {
			return false;
		}
		
		// Code taken from the MOFScript documentation
		ExtendedParserUtil parserUtil = new ExtendedParserUtil();
		parserUtil.setCompilePath(ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/").toFile().getAbsolutePath());
		parserUtil.setInputFileLocation(ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/").toFile().getAbsolutePath());
		parserUtil.parse(mainModule);
        
		// check for errors:
        int errorCount = ParserUtil.getModelChecker().getErrorCount();
        
        if (errorCount == 0) {
        	emptyTempDir();
            return true;            
        }
		
		// If all configurations are incorrect, then return false
		emptyTempDir();
		return false;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.ITransformationEngine#executeTransformation(org.eclipse.emf.ecore.EObject[], es.esi.gemde.modeltransformator.service.ITransformation, java.lang.String)
	 */
	@Override
	public IStatus executeTransformation(EObject[] inputs, ITransformation transformation, String outputPath) throws IllegalArgumentException, TransformationEngineException {
		logs = new String();
		
		if (inputs == null){
			throw new IllegalArgumentException("A null inputs array was provided");
		}
		
		if (transformation == null){
			throw new IllegalArgumentException("A null transformation was provided");
		}
		
		if (outputPath == null){
			throw new IllegalArgumentException("A null outputPath was provided");
		}
		
		if (transformation.getRequiredInputList().size() != inputs.length) {
			throw new IllegalArgumentException("The number of provided inputs doesn't match the number of required inputs for this transformation");
		}
		
		for (int i = 0; i < inputs.length; i++) {
			EClass c = transformation.getRequiredInputList().get(i);
			if (!(c.isInstance(inputs[i]))) {
				throw new IllegalArgumentException("Provided input \"" + inputs[i].eClass().getInstanceTypeName() + "\" doesn't match the required type \"" + c.getName() + "\"");
			}
		}
		
		// Code taken from MOFScript documentation
	    ExecutionManager execMgr = ExecutionManager.getExecutionManager();
	    
	    //
	    // The ParserUtil parses and sets the input transformation model
	    // for the execution manager.
	    //
	    
	    List<URI> uris = transformation.getTransformationURIs();
	    
	    // Copy transformation files to the temporal directory
		Vector<URI> copiedFiles = new Vector<URI>();
		
		for (URI uri : uris) {
			try {
				BufferedReader r = new BufferedReader(new InputStreamReader(uri.toURL().openStream()));
				File outFile = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/" + new File(uri).getName()).toFile();
				BufferedWriter w = new BufferedWriter(new FileWriter(outFile));
				char [] buffer = new char [1024];
				int read;
				while ((read = r.read(buffer)) != -1) {
					w.write(buffer, 0, read);
					w.flush();
				}
				r.close();
				w.close();
				copiedFiles.add(outFile.toURI());
			} catch (Exception e) {
				String message = "Transformation resources couldn't be copied!\n";
				return new Status(IStatus.ERROR, PLUGIN_ID, message); 
			}
		}
		
		// Get the main transformation file
		URI mainModule = getMainTransformation(copiedFiles);
		if (mainModule == null) {
			return new Status(IStatus.ERROR, PLUGIN_ID, "Couldn't find the main module of the transformation."); 
		}
		
		// Code taken from the MOFScript documentation
		ExtendedParserUtil parserUtil = new ExtendedParserUtil();
		parserUtil.setCompilePath(ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/").toFile().getAbsolutePath());
		parserUtil.setInputFileLocation(ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID + "/").toFile().getAbsolutePath());
		parserUtil.parse(mainModule);
        
		// check for errors:
        int errorCount = ParserUtil.getModelChecker().getErrorCount();
	    
	    // check for errors:
	    Iterator<MofScriptParseError> errorIt = ParserUtil.getModelChecker().getErrors(); // Iterator of MofScriptParseError objects
	    if (errorCount > 0) {
	        String message = "Transformation couldn't be run because of errors!\n";
	        while (errorIt.hasNext()) {
	            MofScriptParseError parseError = (MofScriptParseError) errorIt.next();
	            message += "\t \t: Error: " + parseError.toString();
	        }            
	        return new Status(IStatus.ERROR, PLUGIN_ID, message);  
	    } 
	    
	    for (EObject model : inputs) {
	    	// set the source model for the execution manager    
	        execMgr.addSourceModel(model); 
	    }
	    
	    // sets the root output directory, if any is desired
	    execMgr.setRootDirectory(outputPath);
	    
	    // Set the Block comment
	    String blockComment = MofScriptEditorPlugin.getDefault().getPreferenceStore().getString(MofScriptPreferences.BLOCK_COMMENT_TAG);
	    if (blockComment != null && !blockComment.equals("")) {
	    	execMgr.setBlockCommentTag(blockComment);
	    }
	    else {
	    	// If not available set // to the default comment tag
	    	execMgr.setBlockCommentTag("//");
	    }
	    
	    String characterSet = MofScriptEditorPlugin.getDefault().getPreferenceStore().getString(MofScriptPreferences.CHARSET);
	    if (characterSet != null && !characterSet.equals("")) {
	    	execMgr.setCharset(characterSet);
	    }
	    else {
	    	// If not available set 'UTF-8' as default character set
	    	execMgr.setCharset("UTF-8");
	    }
	    
	    // if true, files are not generated to the file system, but populated into a file model
	    // which can be fetched afterwards. Value false will result in standard file generation
	    execMgr.setUseFileModel(false);
	    
	    // Turns on/off system logging 
	    execMgr.setUseLog(false); 
	    
	    try {
	        execMgr.executeTransformation();            
	    } catch (MofScriptExecutionException mex) {
	        throw new TransformationEngineException(mex);
	    }        
	
	    // Everything went OK!!
	    return new Status(IStatus.OK, PLUGIN_ID, logs);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.ITransformationEngine#getName()
	 */
	@Override
	public String getName() {
		return ENGINE_NAME;
	}

	/**
	 * Internal method used to get the transformation file containing the main module from a list.
	 * 
	 * @param copiedFiles the list of files
	 * @return the main transformation module or null if no main transformation modules are defined, or if more than one main module is defined.
	 */
	private URI getMainTransformation(Vector<URI> copiedFiles) {
		URI result = null;
		
		for (URI file : copiedFiles) {
			try {
				BufferedReader r = new BufferedReader(new InputStreamReader(file.toURL().openStream()));
				char [] buffer = new char [1024];
				boolean finished = false;
				boolean mustAppend = false;
				String previousStr = "";
				while (r.read(buffer) != -1 && !finished) {
					String str = new String(buffer);
					if (mustAppend) {
						str = previousStr + str;
						mustAppend = false;
						previousStr = "";
					}
					int start = 0, current;
					while ((current = str.indexOf("::", start)) != -1) {
						if (str.indexOf('(', current) != -1) {
							String main = str.substring(current + 2, str.indexOf('(', current)).trim();
							if (main != null && main.equals("main")) {
								if (result != null) {
									r.close();
									return null;
								}
								result = file;
								finished = true;
							}
						}
						else {
							mustAppend = true;
							previousStr = str;
						}
						start = current + 2;
					}
				}
				r.close();
			}
			catch (IOException ioe) {
				return null;
			}
		}
		
		return result;
	}

	/**
	 * Internal method used to empty the temporal folder of this plugin.
	 */
	private void emptyTempDir() {
		// Go to the temporal MAST tool directory
		File dir = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + PLUGIN_ID).toFile();
		if (dir.exists() && dir.isDirectory()) {
			for (File f : dir.listFiles()) {
				f.delete();
			}
		}
		
	}

	private ExecutionMessageListener messageLogger = new ExecutionMessageListener() {
		
		@Override
		public void executionMessage(String type, String description) {
			//logs += type + " - " + description + "\n";
			logs += description + "\n";
		}
		
	};


	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.ITransformationEngine#getAdditionalOptionsList()
	 */
	@Override
	public String[] getAdditionalOptionsList() {
		return new String[0];
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.ITransformationEngine#checkTransformationParamsValidity(java.util.HashMap, java.util.HashMap, java.util.HashMap)
	 */
	@Override
	public boolean checkTransformationParamsValidity(
			HashMap<String, EClass> inputs, HashMap<String, EClass> outputs,
			HashMap<String, String> options) {
		return inputs.size() > 0;
	}

}
